package com.google.code.expr;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Properties;

import com.google.code.expr.lang.EXPRAtomReader;
import com.google.code.expr.runtime.EXPRStreamParser;

/**
 * <p>InputStream implementation of EXPR.  Accepts an InputStream or Reader,
 * and an optional Properties list of variables.  It applies the EXPR
 * command rules as it reads in the stream, outputting the parsed
 * result as it goes.</p>
 * 
 * <p>The EXPRInputStream can be used to read in source streams
 * and alter them as they are read, using variables, expressions
 * and simple programming commands.  It is useful for altering
 * configuration depending on a simple set of properties.</p>
 * 
 * <h3>EXPR Commands</h3>
 * <p>All EXPR commands are wrapped in double braces: {{ }}. 
 * For example:</p>
 * <pre> {{ echo "test" }} </pre>
 * 
 * <p>EXPR supports the following commands</p>
 * 
 * <p><b>echo</b>: Output a constant of variable to
 * the stream. </p>
 * <pre>{{ echo <i>expression</i> }}</pre>
 * 
 * <p><b>set</b>: Assign a value to a variable.  This
 * variable can be new, or already exit.  The value is an
 * expression. </p>
 * <pre>{{ set <i>variable</i>=<i>expression</i> }}</pre>
 * 
 * <p><b>if</b>: Start a conditional.  If an expression results
 * as true, output the child block. </p>
 * <pre>{{ if (<i>expression</i>) }}</pre>
 * 
 * <p><b>elseif</b>: Continue a conditional.  Must follow an
 * <i>if</i> or <i>elseif</i>.  If all the prior 
 * conditionals fail, and this expression results
 * as true, output the child block. </p>
 * <pre>{{ elseif (<i>expression</i>) }}</pre>
 * 
 * <p><b>else</b>: Continue a conditional.  Must follow an
 * <i>if</i> or <i>elseif</i>, and be the last conditional.
 * If all the prior conditionals fail, output the child block. </p>
 * <pre>{{ else }}</pre>
 * 
 * <p><b>endif</b>: End a conditional.  Must follow an
 * <i>if</i> or <i>elseif</i> or <i>else</i>.</p>
 * <pre>{{ endif }}</pre>
 * 
 * <h3>Variables</h3>
 * 
 * <p>Variables are identified in EXPR using a dollar-sign
 * ($).  For example:</p>
 * 
 * <pre> {{ set $myVar = "variable contents" }} </pre>
 * 
 * <p>In this example, a variable named "$myVar" is assigned
 * a string value of "variable contents".  If this variable
 * were to be sent out the stream:</p>
 * 
 * <pre>{{ echo $myVar }}</pre>
 * 
 * <p>...then the contents "variable contents" would be
 * outputted.</p>
 * 
 * <p>Variables can have any names, as long as they start
 * with a dollar-sign, have the first character be a letter,
 * and all subsequent characters be letters, numbers or an
 * underscore.</p>
 * 
 * <h3>Variable Types</h3>
 * 
 * <p>Only 4 variable types are supported in EXPR:</p>
 * 
 * <p><b>String</b>: Strings are a simple array of characters.
 * They are identified as constants by surrounding them with
 * double-quotes.  In properties files, they are identified
 * by not being numbers or booleans.</p>
 * 
 * <p><b>Long</b>: Integer number.  They are identified as constants
 * and in properties files by being valid integers.</p>
 * 
 * <p><b>Double</b>: Decimal number.  They are identified as constants
 * and in properties files by being valid decimals, and not integers.</p>
 * 
 * <p><b>Boolean</b>: True or False.  They are identified as constants
 * and in properties as either "true" or "false" (without the quotes).</p>
 * 
 * <h3>Expression</h3>
 * 
 * <p>Normal programming language expressions are supported
 * within EXPR.  These will be familiar to developers of languages
 * such as Java and Javascript.</p>
 * 
 * <p>Expressions can be grouped with parentheses, and support the
 * following operators (in precedence order):</p>
 * <ol>
 *  <li> <b>!</b> (not)</li>
 *  <li> <b>+</b> (add), <b>-</b> (minus), <b>*</b> (multiply), <b>/</b> (divide></li>
 *  <li> <b><</b> (less than), <b><=</b> (less than or equal to), <b>></b> (greater than), <b>>=</b> (greater than or equal to)</li>
 *  <li> <b>==</b> (equals), <b>!=</b> (not equals)</li>
 *  <li> <b>&&</b> (and)</li>
 *  <li> <b>^</b> (exclusive or)</li>
 *  <li> <b>||</b> (or) </li>
 * </ol>
 * 
 * <p>Example expressions:</p>
 * 
 * <pre>{{ if ( (100 > 50) && ($myBool != true) ) }} </pre>
 * 
 * <pre>{{ if ( !($myBool) || ($myString == "constant string") ) }} </pre>
 * 
 * <pre>{{ set $myTotal = (100 + 50 / 2) }} </pre>
 * 
 * <h3>Conditionals</h3>
 * 
 * <p>Conditionals are used to output a child block or
 * not.  You surround a second of your content with
 * a conditional, and it will only be outputted if
 * the expression is true.</p>
 * 
 * <p>Conditionals always start with an <b>if</b>
 * command, and always end with an <b>endif</b>
 * command.  The <b>if</b> can be followed by
 * zero or more <b>elseif</b> commands, which
 * can be followed by an optional <b>else</b>
 * command.</p>
 * 
 * <p>At most one of the conditional commands
 * can result in its child block being outputted.
 * the first conditional that results as true
 * is the winner - all subsequent conditionals
 * are ignored.</p>
 * 
 * <p>The child blocks are just more of the stream,
 * including more commands, until the next corresponding
 * conditional is achieved.  The child block can
 * have conditionals as well.</p>
 * 
 * <table>
 *  <tr><th>EXPR</th><th>Output</th></tr>
 *  <tr><td valign=top><pre>
 *   {{if (1==1)}}
 *     First block
 *   {{else}}
 *     Second block
 *   {{endif}}
 *  </pre></td>
 *  <td valign=top>First block</td></tr>
 *  
 *  <tr><td valign=top><pre>
 *   {{if (2==1)}}
 *     First block
 *   {{elseif (3==1)}}
 *     Second block
 *   {{else)}}
 *     Third block
 *   {{endif}}
 *  </pre></td>
 *  <td valign=top>Third block</td></tr>
 *  
 *  <tr><td valign=top><pre>
 *   {{if (1==1)}}
 *     {{if (1==2)}}
 *       Inner First Block
 *     {{else}}
 *       Inner Second Block
 *     {{elseif}}
 *   {{else}}
 *     {{if (1==1)}}
 *       Inner Third Block
 *     {{else}}
 *       Inner Fourth Block
 *     {{elseif}}
 *   {{endif}}
 *  </pre></td>
 *  <td valign=top>Inner Second Block</td></tr>
 *  
 * </table>
 * 
 * @author critchie
 *
 */
public class EXPRInputStream extends InputStream {

	private EXPRStreamParser mStreamParser;
	private StringReader mCurrentStringReader;
	private boolean mDone;
	
	
	/**
	 * <p>Constructs the EXPR InputStream using the InputStream
	 * parameter as the source.  It will pipe the InputStream
	 * through using EXPR parsing.</p>
	 * 
	 * @param pParent the source stream to parse
	 * @throws IOException Either a Reading or EXPR parsing error.
	 */
	public EXPRInputStream(InputStream pParent) throws IOException {
		this(pParent,null);
	}
	
	/**
	 * <p>Constructs the EXPR InputStream using the Reader
	 * parameter as the source.  It will pipe the Reader
	 * through using EXPR parsing.</p>
	 * 
	 * @param pParent the source stream to parse
	 * @throws IOException Either a Reading or EXPR parsing error.
	 */
	public EXPRInputStream(Reader pParent) throws IOException {
		this(pParent,null);
	}
	
	/**
	 * <p>Constructs the EXPR InputStream using the InputStream
	 * parameter as the source, and the variables from
	 * the Properties parameter.  It will pipe the InputStream
	 * through using EXPR parsing.</p>
	 * 
	 * @param pParent the source stream to parse
	 * @param pVariables the initial variables to set
	 * @throws IOException Either a Reading or EXPR parsing error.
	 */
	public EXPRInputStream(InputStream pParent, Properties pVariables) throws IOException {
		mDone = false;
		mStreamParser = new EXPRStreamParser(new EXPRAtomReader(pParent));
		if (pVariables!=null) {
			mStreamParser.setInitialState(pVariables);
		}
		mCurrentStringReader = null;
	}
	
	/**
	 * <p>Constructs the EXPR InputStream using the Reader
	 * parameter as the source, and the variables from
	 * the Properties parameter.  It will pipe the Reader
	 * through using EXPR parsing.</p>
	 * 
	 * @param pParent the source stream to parse
	 * @param pVariables the initial variables to set
	 * @throws IOException Either a Reading or EXPR parsing error.
	 */
	public EXPRInputStream(Reader pParent, Properties pVariables) throws IOException {
		mDone = false;
		mStreamParser = new EXPRStreamParser(new EXPRAtomReader(pParent));
		if (pVariables!=null) {
			mStreamParser.setInitialState(pVariables);
		}
		mCurrentStringReader = null;
	}
	
	 
	
	/**
	 * Reads the next character from the stream, and returns
	 * -1 if the stream is done. This applies the EXPR parsing as
	 * it reads.
	 */
	@Override public int read() throws IOException {
		try {
			if (!mDone) {
				if (mCurrentStringReader==null) {
					String nextString = mStreamParser.readNextStream();
					if (nextString!=null) {
						mCurrentStringReader = new StringReader(nextString);
						return read();
					}
					else {
						mDone = true;
						return -1;
					}
				}
				else {
					int next = mCurrentStringReader.read();
					if (next<0) {
						mCurrentStringReader = null;
						String nextString = mStreamParser.readNextStream();
						if (nextString!=null) {
							mCurrentStringReader = new StringReader(nextString);
							return read();
						}
						else {
							mDone = true;
							return -1;
						}
					}
					else {
						return next;
					}
				}
			}
			else {
				return -1;
			}
		}
		catch (IOException e) {
			mDone = true;
			throw e;
		}
	}

	/**
	 * Closes the internal Reader or Inputstream passed
	 * in.
	 */
	@Override public void close() throws IOException {
		mStreamParser.close();
	}

	public static void main(String[] pArgs) throws Exception {
		FileInputStream fis = new FileInputStream("test/simple.expr");

		Properties state = new Properties();
		state.load(new FileInputStream("test/simple.properties"));
		EXPRInputStream streamReader = new EXPRInputStream(fis,state);
		
		BufferedReader bis = new BufferedReader(new InputStreamReader(streamReader));
		String line = bis.readLine();
		while (line!=null) {
			System.out.println(line);
			line = bis.readLine();
		}

	}
	

}
